<!DOCTYPE html>



  


<html class="theme-next mist use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">










<meta name="description" content="原文链接 对于许多C++开发者来说，他们可能把API设计的优先级排到第三或者第四。大多数拥抱C++的开发者都是因为它的原始能力和控制权。因此，性能和优化占据了他们百分之八十的时间。 当然，每一位C++开发者都考虑头文件设计的各个方面，但是API的设计不仅仅只有头文件的设计。事实上，我强烈推荐每一位开发者多思考API的设计，无论是面向公共的还是面向内部的，它都能节约维护成本，提供平滑升级路径并且帮你">
<meta property="og:type" content="article">
<meta property="og:title" content="[译]25个C++ API设计错误和规避方法">
<meta property="og:url" content="http://yoursite.com/2019/07/17/译-25个C-API设计错误和规避方法/index.html">
<meta property="og:site_name" content="vicvon&#39;s blog">
<meta property="og:description" content="原文链接 对于许多C++开发者来说，他们可能把API设计的优先级排到第三或者第四。大多数拥抱C++的开发者都是因为它的原始能力和控制权。因此，性能和优化占据了他们百分之八十的时间。 当然，每一位C++开发者都考虑头文件设计的各个方面，但是API的设计不仅仅只有头文件的设计。事实上，我强烈推荐每一位开发者多思考API的设计，无论是面向公共的还是面向内部的，它都能节约维护成本，提供平滑升级路径并且帮你">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2019-07-17T14:48:04.071Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="[译]25个C++ API设计错误和规避方法">
<meta name="twitter:description" content="原文链接 对于许多C++开发者来说，他们可能把API设计的优先级排到第三或者第四。大多数拥抱C++的开发者都是因为它的原始能力和控制权。因此，性能和优化占据了他们百分之八十的时间。 当然，每一位C++开发者都考虑头文件设计的各个方面，但是API的设计不仅仅只有头文件的设计。事实上，我强烈推荐每一位开发者多思考API的设计，无论是面向公共的还是面向内部的，它都能节约维护成本，提供平滑升级路径并且帮你">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Mist',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://yoursite.com/2019/07/17/译-25个C-API设计错误和规避方法/">





  <title>[译]25个C++ API设计错误和规避方法 | vicvon's blog</title>
  








</head>

<body itemscope="" itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope="" itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">vicvon's blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br>
            
            关于
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-sitemap">
          <a href="/sitemap.xml" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-sitemap"></i> <br>
            
            站点地图
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope="" itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://yoursite.com/2019/07/17/译-25个C-API设计错误和规避方法/">

    <span hidden itemprop="author" itemscope="" itemtype="http://schema.org/Person">
      <meta itemprop="name" content="vicvon">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/uploads/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope="" itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="vicvon's blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">[译]25个C++ API设计错误和规避方法</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-07-17T22:38:30+08:00">
                2019-07-17
              </time>
            

            

            
          </span>

          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2019/07/17/译-25个C-API设计错误和规避方法/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2019/07/17/译-25个C-API设计错误和规避方法/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p><a href="https://www.acodersjourney.com/top-25-cplusplus-api-design-mistakes-and-how-to-avoid-them/" target="_blank" rel="noopener">原文链接</a></p>
<p>对于许多C++开发者来说，他们可能把API设计的优先级排到第三或者第四。大多数拥抱C++的开发者都是因为它的原始能力和控制权。因此，性能和优化占据了他们百分之八十的时间。</p>
<p>当然，每一位C++开发者都考虑头文件设计的各个方面，但是API的设计不仅仅只有头文件的设计。事实上，我强烈推荐每一位开发者多思考API的设计，无论是面向公共的还是面向内部的，它都能节约维护成本，提供平滑升级路径并且帮你客户省去麻烦。</p>
<p>以下列出的许多错误都是我的个人经验和我从Martin Reddy的精彩书籍《C ++ API Design》（我强烈推荐的书）中学到的东西的结合。如果你真的想深入了解c++ API设计，你应该读这本书，然后使用下面的列表作为更多的清单来强制执行代码审查。</p>
<h4 id="错误1：没有将API放入命名空间"><a href="#错误1：没有将API放入命名空间" class="headerlink" title="错误1：没有将API放入命名空间"></a>错误1：没有将API放入命名空间</h4><p><strong>为什么这是个错误？</strong></p>
<p>因为你不知道哪个代码会使用你的API，尤其是外部API。如果你没有把你的API函数放入命名空间，它可能会导致你的API命名和其他API命名冲突。</p>
<p>让我们来看一个简单的API和客户端类的使用</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//API - In Location.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">vector</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">vector</span>(<span class="keyword">double</span> x, <span class="keyword">double</span> y, <span class="keyword">double</span> z);</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">double</span> xCoordinate;</span><br><span class="line">  <span class="keyword">double</span> yCoordinate;</span><br><span class="line">  <span class="keyword">double</span> zCoordinate;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//Client Program</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdafx.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Location.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; myVector;</span><br><span class="line">  myVector.push_back(<span class="number">99</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果有人试图在一个项目中使用这个类并且也使用了<code>std::vector</code>，他会得到一个错误<code>error C2872: ‘vector’: ambiguous symbol</code>。这是因为编译器不能决定客户端代码中使用哪个<code>vector</code>，是<code>std::vector</code>还是在<code>Location.h</code>中定义的<code>vector</code>对象。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>总是将你的API放在一个定制的命名空间中</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//API</span></span><br><span class="line"><span class="keyword">namespace</span> LocationAPI</span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">vector</span></span></span><br><span class="line"><span class="class">  &#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">vector</span>(<span class="keyword">double</span> x, <span class="keyword">double</span> y, <span class="keyword">double</span> z);</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">double</span> xCoordinate;</span><br><span class="line">    <span class="keyword">double</span> yCoordinate;</span><br><span class="line">    <span class="keyword">double</span> zCoordinate;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>另一个选择是在你公共API符号前加一个唯一的前缀。如果遵守这个约定，我们将调用我们的类<code>Ivector</code>而不是<code>vector</code>。<code>OpenGL</code>和<code>QT</code>就是使用的这个方法。</p>
<p>在我看来，如果你在开发一个纯C的API，这样做是合理的。它有一个麻烦的事就是确保所有你的公共符号是唯一的。如果你使用的是C++，你应该组织你的API到一个命名空间中，让编译器为你做繁重的工作。</p>
<p>我还强烈鼓励使用嵌套的命名空间进行功能分组或者分离公共API和内部API。一个很好的列子就是BOOST库，它使用了嵌套命名空间。例如，在根boost命名空间内，<code>boost::variant</code>包含了公共的BOOST Variant API并且<code>boost::detail::variant</code>包含了内部的API。</p>
<h4 id="错误2：在你公共API头全局作用域内包含了using-namespace"><a href="#错误2：在你公共API头全局作用域内包含了using-namespace" class="headerlink" title="错误2：在你公共API头全局作用域内包含了using namespace"></a>错误2：在你公共API头全局作用域内包含了using namespace</h4><p><strong>为什么这是个错误？</strong></p>
<p>因为这将导致被引用的命名空间中的所有符号变成全局命名空间中可见，并且抵消了第一点使用命名空间的好处。</p>
<p>另外：</p>
<ol>
<li>头文件的使用者不可能取消命名空间包含，因此他们被强制接收你命名空间的决定，这是不可取的。</li>
<li>它显著增加了命名冲突的机会，这也是第一点要解决的。</li>
<li>当引入新版本库后，有可能是程序工作版本编译失败。如果新版本引入的名字和程序正在使用的库中的名字冲突就会导致这种情况发生。</li>
<li>在你包含的头文件中using namespace部分代码从引入的那一点开始生效，意味着任何出现在这之前的代码可能区别对待与出现在这之后的代码。</li>
</ol>
<p><strong>如何解决这个问题？</strong></p>
<ol>
<li>避免使用using namespace声明在你的头文件中。如果你确实需要一些命名空间下的对象，请在你头文件中使用完整名字(如，std::cout)。</li>
</ol>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//File:MyHeader.h:</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span></span></span><br><span class="line"><span class="class">&#123;</span>   </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    Microsoft::WRL::ComPtr _parent;</span><br><span class="line">    Microsoft::WRL::ComPtr _child;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>如果上面推荐的第一条建议导致太多的代码，将using namespace的使用限制在头文件中定义的类或者命名空间下。另一点是像下面这样使用域别名。</li>
</ol>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//File:MyHeader.h:</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">namespace</span> wrl = Microsoft::WRL; <span class="comment">// note the aliasing here !</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    wrl::ComPtr _parent;</span><br><span class="line">    wrl::ComPtr _child;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其他c++头文件问题，请参考<a href="https://www.acodersjourney.com/top-10-c-header-file-mistakes-and-how-to-fix-them/" target="_blank" rel="noopener">Top 10 C++ header file mistakes and how to fix them</a></p>
<h4 id="错误3：忽略三法则"><a href="#错误3：忽略三法则" class="headerlink" title="错误3：忽略三法则"></a>错误3：忽略三法则</h4><p><strong>什么是三法则</strong></p>
<p>三法则是，如果一个类定义了析构函数，拷贝构造函数或赋值构造函数，那么应该显示的定义这3个函数，而不是使用他们的默认实现。</p>
<p><strong>为什么忽略三法则是个错误？</strong></p>
<p>如果你定义了他们中的一个，可能你的类正管理一个资源(内存，文件句柄，套接字)。从而：</p>
<ol>
<li>如果你编写/禁用拷贝构造函数或赋值构造函数，你可能需要对另一个做相同的事: 如果一个处理了“特殊的”工作，另一个也可能需要做同样的事情，因为两个函数应该有相似的效果。</li>
<li>如果现实的写了拷贝函数，你可能需要写析构函数：如果在拷贝构造中的“特殊”工作是分配或者复制一些资源（内存，文件，套接字），你需要在析构函数中释放。</li>
<li>如果你显示的写了析构函数，你可能需要显示写或者禁用拷贝：如果你必须写一个nontrivial析构函数，因为你需要手动释放一个对象持有的资源。如果是这样，很可能这些资源要求仔细复制，并且你需要注意对象拷贝和赋值的方法，或者完全禁用拷贝。</li>
</ol>
<p>下面让我们看一个例子，在下面的API中，我们有一个<code>int*</code>的资源被<code>MyArray</code>类管理。我们给类创建了一个析构函数，因为当我们销毁对象后我们需要释放<code>int*</code>的内存。到目前为主都很好。</p>
<p>现在我们假设你的API客户端如下使用它：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">int</span> vals[<span class="number">4</span>] = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> &#125;;</span><br><span class="line">  <span class="function">MyArray <span class="title">a1</span><span class="params">(<span class="number">4</span>, vals)</span></span>; <span class="comment">// Object on stack - will call destructor once out of scope</span></span><br><span class="line">  <span class="function">MyArray <span class="title">a2</span><span class="params">(a1)</span></span>; <span class="comment">// DANGER !!! - We're copyin the reference to the same object</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>发生了什么？</strong></p>
<p>客户端在栈上通过构造函数创建了一个类的实例a1。然后通过从a1拷贝创建了实例a2。当a1出了作用域，析构函数将删除类内部的<code>int*</code>的内存。但是当a2出作用域后，它调用析构函数并且尝试释放<code>int*</code>内存，这导致double free堆被破坏。</p>
<p>因为我们没有提供拷贝构造函数，并且没有标记我们的API是不可拷贝的，没有办法让客户端知道他不应该拷贝<code>MyArray</code>对象。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>我们可以做以下事情：</p>
<ol>
<li>提供拷贝构造函数，实现对底层资源的深拷贝</li>
<li>使类成为不可拷贝的，通过delete拷贝构造和赋值构造</li>
<li>最后提供API文档信息。</li>
</ol>
<p>这是通过提供拷贝和赋值构造修复上面问题的代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// File: RuleOfThree.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyArray</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">int</span> size;</span><br><span class="line">  <span class="keyword">int</span>* vals;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ~MyArray();</span><br><span class="line">  MyArray(<span class="keyword">int</span> s, <span class="keyword">int</span>* v);</span><br><span class="line">  MyArray(<span class="keyword">const</span> MyArray&amp; a); <span class="comment">// Copy Constructor</span></span><br><span class="line">  MyArray&amp; <span class="keyword">operator</span>=(<span class="keyword">const</span> MyArray&amp; a); <span class="comment">// Copy assignment operator</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// Copy constructor</span></span><br><span class="line">MyArray::MyArray(<span class="keyword">const</span> MyArray &amp;v)</span><br><span class="line">&#123;</span><br><span class="line">  size = v.size;</span><br><span class="line">  vals = <span class="keyword">new</span> <span class="keyword">int</span>[v.size];</span><br><span class="line">  <span class="built_in">std</span>::copy(v.vals, v.vals + size, checked_array_iterator&lt;<span class="keyword">int</span>*&gt;(vals, size));</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Copy Assignment operator</span></span><br><span class="line">MyArray&amp; MyArray::<span class="keyword">operator</span> =(<span class="keyword">const</span> MyArray &amp;v)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (&amp;v != <span class="keyword">this</span>)</span><br><span class="line">  &#123;</span><br><span class="line">    size = v.size;</span><br><span class="line">    vals = <span class="keyword">new</span> <span class="keyword">int</span>[v.size];</span><br><span class="line">    <span class="built_in">std</span>::copy(v.vals, v.vals + size, checked_array_iterator&lt;<span class="keyword">int</span>*&gt;(vals, size));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>第二个方式修复这个问题是使类变成不可拷贝的通过delete拷贝和赋值构造。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// File: RuleOfThree.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyArray</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">int</span> size;</span><br><span class="line">  <span class="keyword">int</span>* vals;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ~MyArray();</span><br><span class="line">  MyArray(<span class="keyword">int</span> s, <span class="keyword">int</span>* v);</span><br><span class="line">  MyArray(<span class="keyword">const</span> MyArray&amp; a) = <span class="keyword">delete</span>;</span><br><span class="line">  MyArray&amp; <span class="keyword">operator</span>=(<span class="keyword">const</span> MyArray&amp; a) = <span class="keyword">delete</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>现在当客户端尝试拷贝类，他将收到一个变异错误：<code>error C2280: ‘MyArray::MyArray(const MyArray &amp;)’: attempting to reference a deleted function</code></p>
<p><strong>C++11 附录</strong></p>
<p>三法则已经转变为5法则，增加移动构造和移动赋值操作。因此在我们的例子中，如果我们使类成为不可拷贝和不可移动，我们必须将移动构造和移动赋值标记为delete。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyArray</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">int</span> size;</span><br><span class="line">  <span class="keyword">int</span>* vals;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ~MyArray();</span><br><span class="line">  MyArray(<span class="keyword">int</span> s, <span class="keyword">int</span>* v);</span><br><span class="line">  <span class="comment">//The class is Non-Copyable</span></span><br><span class="line">  MyArray(<span class="keyword">const</span> MyArray&amp; a) = <span class="keyword">delete</span>;</span><br><span class="line">  MyArray&amp; <span class="keyword">operator</span>=(<span class="keyword">const</span> MyArray&amp; a) = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="comment">// The class is non-movable</span></span><br><span class="line">  MyArray(MyArray&amp;&amp; a) = <span class="keyword">delete</span>;</span><br><span class="line">  MyArray&amp; <span class="keyword">operator</span>=(MyArray&amp;&amp; a) = <span class="keyword">delete</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p><strong><em>附加警告</em></strong>：如果顶一个一个拷贝构造（包括标记为delete），类没有创建移动构造。如果你的类仅仅包含简单的数据类型，并且你计划使用隐式生成的移动构造，那么如果你定义拷贝构造它将是不可能的。这种情况你必须显示的定义移动构造。</p>
<h4 id="错误4：没有在你的API中标记移动构造和移动赋值为noexcept"><a href="#错误4：没有在你的API中标记移动构造和移动赋值为noexcept" class="headerlink" title="错误4：没有在你的API中标记移动构造和移动赋值为noexcept"></a>错误4：没有在你的API中标记移动构造和移动赋值为noexcept</h4><p>一般来说，移动操作不会抛出异常。你基本上是从源对象中窃取一堆指针到目标对象，理论上不应该抛出异常。</p>
<p><strong>为什么这是个错误？</strong></p>
<p>如果移动构造不破坏它强大的异常安全保证，一个stl容器再调整大小时仅仅使用移动构造函数。例如，<code>std::vector</code>不将使用你API对象的移动构造函数，如果移动构造会抛出异常。这是因为如果在移动操作时抛出异常，那被处理的数据可能会丢失，而一个拷贝构造的原始数据不会被修改。</p>
<p>因此，如果在你API中移动构造和移动赋值没有标记为noexcept，它将可能引起性能问题，如果客户端计划使用stl容器的话。<a href="http://www.hlsl.co.uk/blog/2017/12/1/c-noexcept-and-move-constructors-effect-on-performance-in-stl-containers" target="_blank" rel="noopener">这篇文章</a>展示了一个类不能被移动和可以被移动相比，赋值到vector有2倍的时间消耗，并且经历不可预测的内存波动。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>简单的标记移动构造和移动赋值为noexcept</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Tool</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  Tool(Tool &amp;&amp;) <span class="keyword">noexcept</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="错误5：没有将不可抛出异常的API标记为noexcept"><a href="#错误5：没有将不可抛出异常的API标记为noexcept" class="headerlink" title="错误5：没有将不可抛出异常的API标记为noexcept"></a>错误5：没有将不可抛出异常的API标记为noexcept</h4><p><strong>为什么这是一个API设计错误？</strong></p>
<p>将API标记为noexcept有多重好处，包括编译器优化，例如移动构造的优化。然而，从API设计角度看，如果你的API真的不抛出异常，它将减少你客户端代码的复杂度，因为他们不需要有多个try/catch块在他们代码里。这里还有2个额外的好处：</p>
<ol>
<li>客户端不需要写单元测试，去测试异常代码路径</li>
<li>客户端软件的代码覆盖率可能变高，因为减少代码复杂度</li>
</ol>
<p><strong>如何修复这个问题？</strong></p>
<p>仅仅标记不抛异常的API为noexcept</p>
<h4 id="错误6：没有标记单个参数的构造函数为explicit"><a href="#错误6：没有标记单个参数的构造函数为explicit" class="headerlink" title="错误6：没有标记单个参数的构造函数为explicit"></a>错误6：没有标记单个参数的构造函数为explicit</h4><p><strong>为什么这是个API设计错误？</strong></p>
<p>编译被允许做一次隐式转换，将参数解析为函数。这意味着编译器可以使用单参数构造函数转换从一种类型到另一个类型，以获得正确的参数类型。</p>
<p>例如，如果我们在我们的API中有如下的单参数构造函数：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> LocationAPI</span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">vector</span></span></span><br><span class="line"><span class="class">  &#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">vector</span>(<span class="keyword">double</span> x);</span><br><span class="line">    <span class="comment">// .....</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们能通过下面的代码调用：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LocationAPI::<span class="built_in">vector</span> myVect = <span class="number">21.0</span>;</span><br></pre></td></tr></table></figure>
<p>这将调用vector的单参数构造函数通过将21.0转换为参数。然而，这种隐式的行为令人困惑，不直观，并且大多数情况下是意外的。</p>
<p>另一个不需要隐式转换的例子，考虑下面的函数签名：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">CheckXCoordinate</span><span class="params">(<span class="keyword">const</span> LocationAPI::<span class="built_in">vector</span> &amp;coord, <span class="keyword">double</span> xCoord)</span></span>;</span><br></pre></td></tr></table></figure>
<p>如果没有声明单参数构造函数为explicit，我们能像下面这样调用函数：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CheckXCoordinate(<span class="number">20.0</span>, <span class="number">20.0</span>);</span><br></pre></td></tr></table></figure>
<p>这将削弱你API的类型安全，因为现在编译器不强制第一个参数为显示的vector对象。</p>
<p>结果是，用户有可能忘记参数的顺序，并且错误的顺序传递他们。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>这就是为什么你应该使用explicit关键字修饰单参数构造函数，除非你知道你想支持隐式转换。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">vector</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">explicit</span> <span class="title">vector</span><span class="params">(<span class="keyword">double</span> x)</span></span>;</span><br><span class="line">  <span class="comment">//.....</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="错误7：没有标记只读的数据或方法为const"><a href="#错误7：没有标记只读的数据或方法为const" class="headerlink" title="错误7：没有标记只读的数据或方法为const"></a>错误7：没有标记只读的数据或方法为const</h4><p><strong>为什么这是个错误？</strong></p>
<p>有时，你的API会将你的客户端的一些数据结构作为输入。标记方法和参数为const，这告诉客户端你将以只读的方式使用数据。相反地，如果你的API方法和参数没有被标记为const，你的客户端可能传给你一个数据的拷贝，因为你没有保证不修改数据。根据客户端代码调用API的频率，性能的影响可以从轻微到严重。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>当你的API需要以只读的方式使用客户端数据，标记API的方法和参数为const。</p>
<p>我们假设你需要一个函数仅仅检查两个坐标是不是相同：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Don't do this:</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">AreCoordinatesSame</span><span class="params">(<span class="built_in">vector</span>&amp; vect1, <span class="built_in">vector</span>&amp; vect2)</span></span>;</span><br></pre></td></tr></table></figure>
<p>相反，标记方法为const，这就告诉客户端你不会修改客户端传来的vector对象。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">AreCoordinatesSame</span><span class="params">(<span class="built_in">vector</span>&amp; vect1, <span class="built_in">vector</span>&amp; vect2)</span> <span class="keyword">const</span></span>;</span><br></pre></td></tr></table></figure>
<p>const的正确性是一个大的话题，请参考一本好的c++的书或者读一下<a href="https://isocpp.org/wiki/faq/const-correctness" target="_blank" rel="noopener">这个文章</a>的FAQ章节</p>
<h4 id="错误8：通过const引用返回API内部数据"><a href="#错误8：通过const引用返回API内部数据" class="headerlink" title="错误8：通过const引用返回API内部数据"></a>错误8：通过const引用返回API内部数据</h4><p><strong>为什么这是个错误？</strong></p>
<p>从表面上看，通过const引用返回一个对象似乎是双赢的，这是因为：</p>
<ol>
<li>避免了不必要的拷贝</li>
<li>因为是const引用，所以客户端无法修改数据</li>
</ol>
<p>然而，这可能导致一些严重问题，</p>
<ol>
<li>如果对象在内部释放了，这时客户端使用的引用怎么办？</li>
<li>客户端抛弃了对象的常量性并且修改它，怎么办？</li>
</ol>
<p><strong>如何解决这个问题？</strong></p>
<p>遵循以下三步：</p>
<ol>
<li>首先，不要通过更好的设计暴露API内部对象</li>
<li>如果第一条代价太大，考虑通过值传递返回对象</li>
<li>如果它是一个堆上分配的对象，考虑返回一个智能指针，确保即使你释放了也能保证对象可以访问</li>
</ol>
<h4 id="错误9：当使用隐式模板实例化时，通过模板实现细节混淆公共头文件"><a href="#错误9：当使用隐式模板实例化时，通过模板实现细节混淆公共头文件" class="headerlink" title="错误9：当使用隐式模板实例化时，通过模板实现细节混淆公共头文件"></a>错误9：当使用隐式模板实例化时，通过模板实现细节混淆公共头文件</h4><p>在隐式实例化中，你模板的内部代码不得不放入头文件中。没有办法绕开它。然而，你可以分离模板声明（你的API用户将引用的）和模板实例，通过将模板实例放在另一个头文件中。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// File: Stack.h ( Public interface)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> STACK_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> STACK_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stack</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">Push</span><span class="params">(T val)</span></span>;</span><br><span class="line">  <span class="function">T <span class="title">Pop</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">bool</span> <span class="title">IsEmpty</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="built_in">std</span>::<span class="built_in">vector</span>&lt;T&gt; mStack;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="keyword">int</span>&gt; IntStack;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="keyword">double</span>&gt; DoubleStack;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="built_in">std</span>::<span class="built_in">string</span>&gt; StringStack;</span><br><span class="line"><span class="comment">// isolate all implementation details within a separate header</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stack_priv.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// File: Stack_priv.h ( hides implementation details of the Stack class)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> STACK_PRIV_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> STACK_PRIV_H</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">void</span> Stack&lt;T&gt;::Push(T val)</span><br><span class="line">&#123;</span><br><span class="line">  mStack.push_back(val);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">T Stack&lt;T&gt;::Pop()</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (IsEmpty())</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">return</span> T();</span><br><span class="line">  &#125;</span><br><span class="line">  T val = mStack.back();</span><br><span class="line">  mStack.pop_back();</span><br><span class="line">  <span class="keyword">return</span> val;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">bool</span> Stack&lt;T&gt;::IsEmpty() <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">return</span> mStack.empty();</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure>
<p>这个技术被用在许多高质量的基于模板的API中，例如boost。它的好处就是保持主要的公共头文件和实现细节分开。</p>
<h4 id="错误10：当使用场景已知的情况不使用显示模板实例化"><a href="#错误10：当使用场景已知的情况不使用显示模板实例化" class="headerlink" title="错误10：当使用场景已知的情况不使用显示模板实例化"></a>错误10：当使用场景已知的情况不使用显示模板实例化</h4><p><strong>为什么这是个错误？</strong></p>
<p>从API设计角度，隐式实例化有如下问题：</p>
<ol>
<li>编译器有责任延迟实例化在正确的位置，并且确保仅仅只有一份代码拷贝，防止符号重复的链接错误。这会浪费你客户端编译和链接的时间。</li>
<li>你内部代码逻辑现在暴露出来了，这不是一个好的主意</li>
<li>客户端可以使用任意的类型实例化你的模板，这些类型都是你没有测试过的，并且会运行失败。</li>
</ol>
<p><strong>如何解决这个问题？</strong></p>
<p>如果你知道你的模板仅仅被用在int，double和string类型，你可以显示实例化生成这3个类型的模板特化。它缩短你客户端编译的时间，和你未经测试的类型分离，保持你模板代码逻辑因此在你的cpp文件中。</p>
<p>要做到这点很简单，只需要三步：</p>
<p><strong><em>Step 1:</em></strong> 将stack模板实现移到cpp文件中</p>
<p>在这点上，我们尝试实例化并且使用push方法，</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Stack&lt;<span class="keyword">int</span>&gt; myStack;</span><br><span class="line">myStack.Push(<span class="number">31</span>);</span><br></pre></td></tr></table></figure>
<p>我们将得到一个链接错误：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">error LNK2001: unresolved external symbol <span class="string">"public: void __thiscall Stack&lt;int&gt;::Push(int)"</span> (?Push@?$Stack@H@@QAEXH@Z)</span><br></pre></td></tr></table></figure>
<p>这个链接错误是告诉我们，它可能没有找到push方法。因为我们没有实例化它。</p>
<p><strong><em>Step 2:</em></strong> 创建一个模板实例（int，double，string类型）在你cpp文件底部：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// explicit template instantiations</span></span><br><span class="line"><span class="keyword">template</span> <span class="class"><span class="keyword">class</span> <span class="title">Stack</span>&lt;int&gt;;</span></span><br><span class="line"><span class="keyword">template</span> <span class="class"><span class="keyword">class</span> <span class="title">Stack</span>&lt;double&gt;;</span></span><br><span class="line"><span class="keyword">template</span> <span class="class"><span class="keyword">class</span> <span class="title">Stack</span>&lt;std::string&gt;;</span></span><br></pre></td></tr></table></figure>
<p>现在你可以编译和运行stack代码。</p>
<p><strong><em>Step 3:</em></strong> 告诉你API客户端你支持3个类型的特化，并且将下面的typedef放在你头文件结尾：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="keyword">int</span>&gt; IntStack;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="keyword">double</span>&gt; DoubleStack;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="built_in">std</span>::<span class="built_in">string</span>&gt; StringStack;</span><br></pre></td></tr></table></figure>
<p><strong><em>警告:</em></strong> 如果你已经显示特化了，客户端将无法创建更多的特化（并且编译器也无法创建隐式实例），因为实现细节隐藏在你的cpp文件中。请确保这是你API预期的。</p>
<h4 id="错误11：暴露内部值在默认函数参数中"><a href="#错误11：暴露内部值在默认函数参数中" class="headerlink" title="错误11：暴露内部值在默认函数参数中"></a>错误11：暴露内部值在默认函数参数中</h4><p><strong>为什么这是个问题？</strong></p>
<p>默认参数常常用来在新版本中扩展API的功能，以便不会破坏API的向后兼容性。</p>
<p>例如，你发布了一个API，</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Constructor</span></span><br><span class="line">Circle(<span class="keyword">double</span> x, <span class="keyword">double</span> y);</span><br></pre></td></tr></table></figure>
<p>后来你决定指定一个radius作为参数很有用。因此你新版本的API使用radius作为第三个参数，然而，你不想破坏已存在的客户端，所以你将radius作为默认参数：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// New API constructor</span></span><br><span class="line">Circle(<span class="keyword">double</span> x, <span class="keyword">double</span> y, <span class="keyword">double</span> radius=<span class="number">10.0</span>);</span><br></pre></td></tr></table></figure>
<p>用这种方法，任何使用你API传了x和y坐标的客户端都可以使用它。这种方式听起来很好。</p>
<p>然而，它面临了多个问题：</p>
<ol>
<li>它将破坏二进制兼容性（ABI），因为方法的符号命名被改变</li>
<li>默认值将编译进你的客户端程序中。这意味着你的客户端必须重新编译他们的代码，如果你发布新版本API并且默认值变了</li>
<li>多个默认参数可能导致客户端使用你API造成错误。例如，如果你为所有参数都提供默认参数，客户端可能错误的使用一个组合，这个组合没有一点逻辑关系，像下面这样提供了x的值，没有提供y的值</li>
</ol>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Circle(<span class="keyword">double</span> x=<span class="number">0</span>, <span class="keyword">double</span> y=<span class="number">0</span>, <span class="keyword">double</span> radius=<span class="number">10.0</span>);</span><br><span class="line"><span class="function">Circle <span class="title">c2</span><span class="params">(<span class="number">2.3</span>)</span></span>; <span class="comment">// Does it make sense to have an x value without an Y value? May or may not !</span></span><br></pre></td></tr></table></figure>
<ol start="4">
<li>最后，当你没有显示指定radius的值时，你就暴露了API的行为。这将是糟糕的，因为如果你后期增加对不同单位的支持，让用户在米，厘米，毫米之间切换。这种情况默认的radius的值10.0将不适用所有的单位。</li>
</ol>
<p><strong>如何解决这个问题？</strong></p>
<p>提供多个重载版本而不是使用默认参数，例如</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Circle();</span><br><span class="line">Circle(<span class="keyword">double</span> x, <span class="keyword">double</span> y);</span><br><span class="line">Circle(<span class="keyword">double</span> x, <span class="keyword">double</span> y, <span class="keyword">double</span> radius);</span><br></pre></td></tr></table></figure>
<p>前两个构造函数的实现可以使用没有指定默认值的属性。重要的是，这些默认值在cpp文件中被指定，并且没有暴露在头文件中。最后，后期的API版本可以改变这些值而对公共的接口没有任何影响。</p>
<p><strong>补充说明</strong></p>
<ol>
<li>不需要将所有的默认参数实例都转换为重载方法。特别地，如果默认参数代表一个非法值或者一个空值，例如定义null作为指针的默认值或者“”作为字符串的值，那么这种用法在API的版本间不可能改变。</li>
<li>作为性能说明，你应该避免定义涉及构造临时对象的默认参数，因为这将通过值传递到方法中，这是有代价的。</li>
</ol>
<h4 id="错误12：C-API使用-define"><a href="#错误12：C-API使用-define" class="headerlink" title="错误12：C++API使用#define"></a>错误12：C++API使用#define</h4><p>#define被用于在C代码中定义常量，如：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> GRAVITY 9.8f</span></span><br></pre></td></tr></table></figure>
<p><strong>为什么这是个错误？</strong></p>
<p>在C++中，你不应该使用#define定义内部常量，有如下几个原因：</p>
<ol>
<li>使用#define在你公共头文件中将泄漏你的实现细节。</li>
<li>#define定义的常量不能进行类型检查，而且容易让我们被隐式转换和舍入错误影响。</li>
<li>#define语句是全局的不限制作用域，例如在单个类中。从而他们能污染你客户的全局命名空间。他们将不得不跳过多步通过#undef去除#define。但是找到正确的#undef位置总是很困难，因为有别的依赖。</li>
<li>#define没有访问控制。你不能定义#define为public，protected或者private。它总是public的。你不能使用#define指定一个智能被派生类访问的常量。</li>
<li>上面的#define定义的GRVAITY符号会被预处理删除，因此它不会进入符号表。这在调试的时候会有巨大的问题，因为在你客户端使用你API调试代码时会隐藏有价值的信息，因为他们只能在调试器中看到9.8这个值，没有任何描述。</li>
</ol>
<p><strong>如何解决这个问题？</strong></p>
<p>对于简单的常量，使用<code>static const</code>代替#define，如</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">float</span> Gravity;</span><br></pre></td></tr></table></figure>
<p>更好的是，如果在编译期知道这个值，使用constexpr</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">constexpr</span> <span class="keyword">double</span> Gravity = <span class="number">9.81</span>;</span><br></pre></td></tr></table></figure>
<p>更多关于const和constexpr请参考<a href="https://stackoverflow.com/questions/13346879/const-vs-constexpr-on-variables" target="_blank" rel="noopener">这篇文章</a>。</p>
<p>在C代码中，有时#define用来定义网络状态：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BATCHING 1</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SENDING 2</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> WAITING 3</span></span><br></pre></td></tr></table></figure>
<p>在c++中，我们可以使用枚举</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="class"><span class="keyword">class</span> <span class="title">NetworkState</span> &#123;</span> Batching, Sending, Waiting &#125;;  <span class="comment">// enum class</span></span><br></pre></td></tr></table></figure>
<h4 id="错误13：使用友元类"><a href="#错误13：使用友元类" class="headerlink" title="错误13：使用友元类"></a>错误13：使用友元类</h4><p>在c++中，友元是你的类授权给另一个类或者方法完全的访问权限的方法。友元类或者友元函数可以访问你的类中的所有protected和private成员。</p>
<p>虽然这是面向对象的设计和封装，这在实践中也很有用。如果你正在开发一个包含多个组件的大型系统，并且想暴露一个组件的功能给选择的客户端，友元会使事情变得简单。</p>
<p>事实上，.NET的[InternalsVisible]属性确实有相似作用。</p>
<p>然而，友元类不应该暴露在公共API中。</p>
<p><strong>为什么这是个错误？</strong></p>
<p>因为在公共API中使用友元就意味着允许客户端破坏你的封装，并且非预期的使用系统对象。 </p>
<p>即使我们放弃了内部发现API的一般问题，客户端也可能以非预期的方式使用API，使用他们的系统然后致电你的支持团队修复阿门以非预期方式使用API的问题。</p>
<p><strong><em>这是他们的问题吗？不！</em></strong>这是你的问题，是你暴露了友元类。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>避免在公共API中使用友元。它们通常是设计不佳的表现，并且允许客户端访问API的所有受保护和私有成员。</p>
<h4 id="错误14：没有避免不必要的头文件包含"><a href="#错误14：没有避免不必要的头文件包含" class="headerlink" title="错误14：没有避免不必要的头文件包含"></a>错误14：没有避免不必要的头文件包含</h4><p><strong>为什么这是个问题？</strong></p>
<p>不必要的头文件会增加编译时间。这不仅仅是浪费使用你API编译代码的开发人员时间，而且会因为自动化构建周期增加而增加成本，这可能需要每天构建几千遍。</p>
<p>另外，有庞大头文件将降低并行构建效率如incredibuild和fastbuild</p>
<p><strong>如何解决这个问题？</strong></p>
<ol>
<li>你的API应该只包含编译需要的头文件。使用前置声明是有用的，因为它减少了编译时间，能打破头文件的循环依赖。</li>
<li>使用预编译头也能有效减少编译时间。</li>
</ol>
<h4 id="错误15：使用前置声明声明外部类型-非你的"><a href="#错误15：使用前置声明声明外部类型-非你的" class="headerlink" title="错误15：使用前置声明声明外部类型(非你的)"></a>错误15：使用前置声明声明外部类型(非你的)</h4><p><strong>为什么这是个问题？</strong></p>
<p>使用前置声明声明不是你的对象会以不可预测的方式破坏客户端代码。例如，如果客户端决定使用一个不同版本的外部API头文件，那么如果你前置声明的外部对象被改变为typedef或者模板类，你的前置声明就被破坏了。</p>
<p>从另一个角度看，如果你从外部头文件前置声明一个类，你就锁定了你客户端使用外部头文件的版本，必须和你使用版本一致，他再也不能升级那个外部依赖了。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>你应该只前置声明你自己API内的符号。也不要前置声明stl类型。<a href="https://stackoverflow.com/questions/47801590/what-are-the-risks-to-massively-forward-declaration-classes-in-header-files" target="_blank" rel="noopener">请看关于这个问题的延伸讨论</a>。</p>
<h4 id="错误16：头文件无法自编译"><a href="#错误16：头文件无法自编译" class="headerlink" title="错误16：头文件无法自编译"></a>错误16：头文件无法自编译</h4><p>一个头文件应该有编译它自己的所有东西，它应该显示#include或者前置声明一个它编译需要的类型。</p>
<p>如果头文件没有它编译需要的所有内容，但是包含头文件的程序编译表明，由于包含顺序依赖，头文件以某种方式获得了它所需要的东西。这通常是因为另一个头文件在编译链之前，它提供了这个头文件缺失的内容。</p>
<p>如果包含顺序依赖关系改变，整个程序可能被破坏。C++编译器因为误导错误信息而臭名昭著，它可能不好定位到问题。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>检查你的头文件通过一个隔离的方式编译他们，建立一个cpp文件仅仅包含你的头文件。如果它产生编译错误，那么需要包含头文件或者前置声明。对项目中的所有头文件以自底向上的方式重复。随着代码库变大和代码块的移动，这将有助于防止随机构建的中断。</p>
<h4 id="错误17：没有提供你API的版本信息"><a href="#错误17：没有提供你API的版本信息" class="headerlink" title="错误17：没有提供你API的版本信息"></a>错误17：没有提供你API的版本信息</h4><p>客户端应该能在编译时和运行时检查集成到他们系统中的API版本。如果信息缺失，他们将无法采取有效错误升级或者打补丁。</p>
<p>他们也很困难在不同平台上增加向后兼容性。</p>
<p>此外，产品的版本号是我们的工程师在回答用户问题时首先要问的。</p>
<h4 id="错误18：从开始就没有决定静态还是动态库实现"><a href="#错误18：从开始就没有决定静态还是动态库实现" class="headerlink" title="错误18：从开始就没有决定静态还是动态库实现"></a>错误18：从开始就没有决定静态还是动态库实现</h4><p><a href="https://www.acodersjourney.com/cplusplus-static-vs-dynamic-libraries/" target="_blank" rel="noopener">你的客户端喜欢使用静态库还是动态库都应该决定你设计的选择</a>。例如：</p>
<ol>
<li>你可以在你的API接口中使用stl类型吗？如果你以静态库的方式发布可能会好点，但是如果是以动态库的方式，可能因为平台类型和编译器版本引起二进制问题。如果是DLL，可能喜欢扁平的C风格API。</li>
<li>有多少功能会进入你的API？对于静态库，你不需要担心太多，因为仅仅只需要库文件被链接进可执行程序中。另一方面，对于DLL，尽管只有5%的功能被使用，整个DLL都被加载到低效的进程空间。如果你使用DLL，把功能分解到多个DLL中可能更好（例如一个math库，你可以从三角函数库中分离出微积分库）。</li>
</ol>
<p><strong>如何解决这个问题？</strong></p>
<p>没有什么神奇之处，它归结起来就是清楚地收集旧需求，在早期讨论阶段确认你的客户需要静态库还是动态库。</p>
<h4 id="错误19：没有意识到ABI兼容性"><a href="#错误19：没有意识到ABI兼容性" class="headerlink" title="错误19：没有意识到ABI兼容性"></a>错误19：没有意识到ABI兼容性</h4><p>根据维基百科对ABI的定义，ABI是两个二进制程序模块之间的接口。通常，其中一个模块是库或者操作系统的工具，另一个是用户执行的程序。</p>
<p>如果一个链接旧版库的程序链接了新版本库能继续运行并且不需要重新编译，则这个库是二进制兼容的。</p>
<p>二进制兼容可以节省很多麻烦。它使在特定平台上发布软件更容易。如果没有确保发布版本的二进制兼容性，人们将强制提供静态链接文件。静态二进制文件很糟糕，因为它们浪费资源(尤其是内存)不允许从库的bug修复和扩展中获益。windows子系统被打包成DLL集合是有原因的，这使得windows更新容易，可能不是真的，而是有别的原因。</p>
<p>例如，这是2个不同函数的name mangling(符号名被用来标识对象和库文件):</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// version 1.0</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetAudio</span><span class="params">(IAudio *audioStream)</span> <span class="comment">//[Name Mangling] -&gt;_Z8SetAudioP5Audio</span></span></span><br><span class="line"><span class="function"><span class="comment">// version 1.1</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetAudio</span><span class="params">(IAudio *audioStream, <span class="keyword">bool</span> high_frequency = <span class="literal">false</span>)</span> <span class="comment">// [Name Mangling] -&gt;_Z8SetAudioP5Audiob</span></span></span><br></pre></td></tr></table></figure>
<p>这两个方法是源代码兼容的，但是不是二进制兼容的，他们的name mangling是不同的。这意味着1.1版本不能简单的替换1.0版本，因为1.0版本的name mangling没有被定义。</p>
<p><strong>如何解决ABI问题？</strong></p>
<p>首先，<a href="https://www.acodersjourney.com/20-abi-breaking-changes/" target="_blank" rel="noopener">熟悉ABI兼容性和破坏ABI的改变</a>。然后，按照Martin Reddy在他的书中提供的附加指导：</p>
<ol>
<li>使用原始C风格的API可以很简单的保证ABI兼容性，因为C语言没有继承，可选参数，重载，异常和模板这些特性。例如，使用<code>std::string</code>可能在不同编译器间没有ABI兼容。为了更好的利用这两个方面，你可以使用C++面向对象的方式开发API，然后提供一个用C语言包装C++的API。</li>
<li>如果你确实有ABI不兼容的更改，你可以考虑在新的库中使用不同的name mangling以便不打破存在应用的ABI兼容性。这种方法被libz使用。在windows上1.1.4版本之前命名为ZLIB.DLL。然而，二进制不兼容的设置被用在之后的版本，所以库被命名为ZLIB1.DLL，这里的“1”显示了API的主版本号。</li>
<li>pimpl idom能保留你接口的二进制兼容，因为它移动所有在将来可能有修改的实现细节到cpp文件中，这些都不会影响h文件。</li>
<li>你可以定义一个重载版本代替在现有版本上加参数。这保证了原来的符号继续有效，并且提供了一个新的。在你的cpp文件内，老版本可以通过调用新的重载版本实现。</li>
</ol>
<h4 id="错误20：在已经发布的API类中添加纯虚方法"><a href="#错误20：在已经发布的API类中添加纯虚方法" class="headerlink" title="错误20：在已经发布的API类中添加纯虚方法"></a>错误20：在已经发布的API类中添加纯虚方法</h4><p><strong>为什么这是个错误？</strong></p>
<p>看如下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubClassMe</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">virtual</span> ~SubClassMe();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">ExistingCall</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">NewCall</span><span class="params">()</span> </span>= <span class="number">0</span>; <span class="comment">// added in new release of API</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>这个API让所有现存的客户端都改变了，因为现在他们必须实现这个新添加的方法，否则他们的派生类将编译失败。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>简单修复就是在抽象类中新添加的方法都实现一个默认实现，保证他们是虚的而不是纯虚的。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubClassMe</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">virtual</span> ~SubClassMe();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">ExistingCall</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">NewCall</span><span class="params">()</span></span>; <span class="comment">// added in new release of API</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="错误21：没有文档说明是同步还是异步"><a href="#错误21：没有文档说明是同步还是异步" class="headerlink" title="错误21：没有文档说明是同步还是异步"></a>错误21：没有文档说明是同步还是异步</h4><p>看如下的头文件中的代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">ExecuteRequest</span><span class="params">(CallRequestContainer&amp; reqContainer)</span></span>;</span><br></pre></td></tr></table></figure>
<p>当我看到它的时候，我完全不知道这个方法是同步的还是异步的。这非常影响我怎么使用和如何使用这个代码。例如，如果这是一个同步调用，我绝对不会在像游戏场景渲染这样关键代码路径使用它。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>这里有一些帮助：</p>
<ol>
<li>使用C++11的特征，如<code>future</code>作为返回值，就显示这是一个异步方法：</li>
</ol>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">std</span>::future&lt;StatusCode&gt; ExecuteRequest(CallRequestContainer&amp; reqContainer);</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>方法名上增加Sync或者Async标识</li>
</ol>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">ExecuteRequestAsync</span><span class="params">(CallRequestContainer&amp; reqContainer)</span></span>;</span><br></pre></td></tr></table></figure>
<ol start="3">
<li>在方法上有注释表明它是同步的还是异步的。</li>
</ol>
<h4 id="错误22：没有使用平台-编译器最低的公共支持"><a href="#错误22：没有使用平台-编译器最低的公共支持" class="headerlink" title="错误22：没有使用平台/编译器最低的公共支持"></a>错误22：没有使用平台/编译器最低的公共支持</h4><p>你应该了解你的客户使用哪个版本的编译器或者C++标准。例如，如果你了解到你的许多客户都是使用的C++11，你就不应该使用C++14的特性。</p>
<p>我们最近收到了客户提交的支持请求，他们使用的老版本的visual studio并且c++14的<code>make_unique</code>不可用。我们不得不使用条件编译修复这个问题，幸运的是这只有很少的地方使用。</p>
<h4 id="错误23：不考虑开源项目的header-only实现"><a href="#错误23：不考虑开源项目的header-only实现" class="headerlink" title="错误23：不考虑开源项目的header only实现"></a>错误23：不考虑开源项目的header only实现</h4><p>如果你以源码发布你的API，请考虑使用header only。</p>
<p>使用header only发布有如下好处：</p>
<ol>
<li>你不必担心在不同平台发布.lib和.dll/.so 文件，也不用担心编译器版本问题。这减少了你构建和发布的逻辑。</li>
<li>你的客户可以访问你的所有源码。</li>
<li>你的客户节省了编译你二进制的步骤，并且确保和他的exe相同的设置。</li>
<li>你的客户节省了打包你二进制包的时间。打包二进制会非常的麻烦，例如游戏引擎。</li>
<li>有些情况header only是唯一的选择，例如处理模板(除非你选择显示特化)</li>
</ol>
<p>这是被许多开源项目使用的非常流行的模式，包括boost和rapidjson。</p>
<h4 id="错误24：参数类型不一致"><a href="#错误24：参数类型不一致" class="headerlink" title="错误24：参数类型不一致"></a>错误24：参数类型不一致</h4><p>这是最近我们审核继承的历史代码出现的问题。</p>
<p>头文件有如下定义：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="keyword">int</span>&gt; IntStack;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="keyword">double</span>&gt; DoubleStack;</span><br><span class="line"><span class="keyword">typedef</span> Stack&lt;<span class="built_in">std</span>::<span class="built_in">string</span>&gt; StringStack;</span><br></pre></td></tr></table></figure>
<p>在代码库中分散了一些没有显示使用<code>typedef</code>和<code>Stack&lt;T&gt;</code>的代码。一个公共方法，有如下声明：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">CheckStackFidelity</span><span class="params">(IntStack testIntStack, Stack&lt;<span class="built_in">std</span>::<span class="built_in">string</span>&gt; testStringStack)</span></span>;</span><br></pre></td></tr></table></figure>
<p><strong>如何解决这个问题？</strong></p>
<p>选择<code>typedef</code>或者非<code>typedef</code>没有关系。关键是“stay consistent”保持一致。</p>
<h4 id="错误25：没有API-review流程"><a href="#错误25：没有API-review流程" class="headerlink" title="错误25：没有API review流程"></a>错误25：没有API review流程</h4><p>在开发阶段，我常常看到没有及早的进行API review。这是因为没有任何结构化的制定去执行API review。</p>
<p>我发现当没有流程的时候会有多重问题：</p>
<ol>
<li>API不符合Beta客户的使用情景。</li>
<li>API和系统或相同产品的其他部分不相似。</li>
<li>API有法律/合规/营销问题。我们遇到过这样一种情况：其中一个API的命名不是很合适。</li>
</ol>
<p>市场需要它，它导致了很多后期重构和延迟。</p>
<p><strong>如何解决这个问题？</strong></p>
<p>为了避免上面指出的几种麻烦，你应该建立一个至少执行以下操作的过程：</p>
<ol>
<li>API设计应该先于编码。在C ++上下文中，这通常是带有相关用户文档的头文件。</li>
<li>所有的相关人员都应该review API，包括合作伙伴团队、Beta（私人预览客户）、营销、法律和开发人员（如果贵公司有）。</li>
<li>在私人预览前几个月与＃2中的所有利益相关者进行另一次API审核，以确保他们满意。</li>
<li>明确告知，任何API私有预览更改都是有代价的，人们应该在开发的早期阶段提出他们的建议。</li>
</ol>
<p>好吧，这些就是我注意到的C++API设计的25个错误。这份清单并不全面，所以你一定要拿一本Martin Reddy的书来深入了解这个主题。</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2019/02/26/Effective-cpp/" rel="next" title="Effective cpp">
                <i class="fa fa-chevron-left"></i> Effective cpp
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2019/08/27/cpp编译器检查函数参数/" rel="prev" title="c++编译器检查函数参数">
                c++编译器检查函数参数 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
      <div id="disqus_thread">
        <noscript>
          Please enable JavaScript to view the
          <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a>
        </noscript>
      </div>
    </div>

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image" src="/uploads/avatar.jpg" alt="vicvon">
            
              <p class="site-author-name" itemprop="name">vicvon</p>
              <p class="site-description motion-element" itemprop="description">记录点滴，慢慢成长</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">27</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">6</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">1</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://github.com/vicvon" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
            </div>
          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-4"><a class="nav-link" href="#错误1：没有将API放入命名空间"><span class="nav-number">1.</span> <span class="nav-text">错误1：没有将API放入命名空间</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误2：在你公共API头全局作用域内包含了using-namespace"><span class="nav-number">2.</span> <span class="nav-text">错误2：在你公共API头全局作用域内包含了using namespace</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误3：忽略三法则"><span class="nav-number">3.</span> <span class="nav-text">错误3：忽略三法则</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误4：没有在你的API中标记移动构造和移动赋值为noexcept"><span class="nav-number">4.</span> <span class="nav-text">错误4：没有在你的API中标记移动构造和移动赋值为noexcept</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误5：没有将不可抛出异常的API标记为noexcept"><span class="nav-number">5.</span> <span class="nav-text">错误5：没有将不可抛出异常的API标记为noexcept</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误6：没有标记单个参数的构造函数为explicit"><span class="nav-number">6.</span> <span class="nav-text">错误6：没有标记单个参数的构造函数为explicit</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误7：没有标记只读的数据或方法为const"><span class="nav-number">7.</span> <span class="nav-text">错误7：没有标记只读的数据或方法为const</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误8：通过const引用返回API内部数据"><span class="nav-number">8.</span> <span class="nav-text">错误8：通过const引用返回API内部数据</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误9：当使用隐式模板实例化时，通过模板实现细节混淆公共头文件"><span class="nav-number">9.</span> <span class="nav-text">错误9：当使用隐式模板实例化时，通过模板实现细节混淆公共头文件</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误10：当使用场景已知的情况不使用显示模板实例化"><span class="nav-number">10.</span> <span class="nav-text">错误10：当使用场景已知的情况不使用显示模板实例化</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误11：暴露内部值在默认函数参数中"><span class="nav-number">11.</span> <span class="nav-text">错误11：暴露内部值在默认函数参数中</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误12：C-API使用-define"><span class="nav-number">12.</span> <span class="nav-text">错误12：C++API使用#define</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误13：使用友元类"><span class="nav-number">13.</span> <span class="nav-text">错误13：使用友元类</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误14：没有避免不必要的头文件包含"><span class="nav-number">14.</span> <span class="nav-text">错误14：没有避免不必要的头文件包含</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误15：使用前置声明声明外部类型-非你的"><span class="nav-number">15.</span> <span class="nav-text">错误15：使用前置声明声明外部类型(非你的)</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误16：头文件无法自编译"><span class="nav-number">16.</span> <span class="nav-text">错误16：头文件无法自编译</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误17：没有提供你API的版本信息"><span class="nav-number">17.</span> <span class="nav-text">错误17：没有提供你API的版本信息</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误18：从开始就没有决定静态还是动态库实现"><span class="nav-number">18.</span> <span class="nav-text">错误18：从开始就没有决定静态还是动态库实现</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误19：没有意识到ABI兼容性"><span class="nav-number">19.</span> <span class="nav-text">错误19：没有意识到ABI兼容性</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误20：在已经发布的API类中添加纯虚方法"><span class="nav-number">20.</span> <span class="nav-text">错误20：在已经发布的API类中添加纯虚方法</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误21：没有文档说明是同步还是异步"><span class="nav-number">21.</span> <span class="nav-text">错误21：没有文档说明是同步还是异步</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误22：没有使用平台-编译器最低的公共支持"><span class="nav-number">22.</span> <span class="nav-text">错误22：没有使用平台/编译器最低的公共支持</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误23：不考虑开源项目的header-only实现"><span class="nav-number">23.</span> <span class="nav-text">错误23：不考虑开源项目的header only实现</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误24：参数类型不一致"><span class="nav-number">24.</span> <span class="nav-text">错误24：参数类型不一致</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误25：没有API-review流程"><span class="nav-number">25.</span> <span class="nav-text">错误25：没有API review流程</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">true</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Mist</a> v5.1.4</div>




        




  <script type="text/javascript">
    (function() {
      var hm = document.createElement("script");
      hm.src = "//tajs.qq.com/stats?sId=63333898";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  

    
      <script id="dsq-count-scr" src="https://vicvon1.disqus.com/count.js" async></script>
    

    
      <script type="text/javascript">
        var disqus_config = function () {
          this.page.url = 'http://yoursite.com/2019/07/17/译-25个C-API设计错误和规避方法/';
          this.page.identifier = '2019/07/17/译-25个C-API设计错误和规避方法/';
          this.page.title = '[译]25个C++ API设计错误和规避方法';
        };
        var d = document, s = d.createElement('script');
        s.src = 'https://vicvon1.disqus.com/embed.js';
        s.setAttribute('data-timestamp', '' + +new Date());
        (d.head || d.body).appendChild(s);
      </script>
    

  




	





  














  





  

  

  

  
  

  

  

  

</body>
</html>
